vorheriges KapitelInhaltsverzeichnisStichwortverzeichnisFeedbacknächstes Kapitel


Woche 3

Tag 15



Dateien und E/A

Die ersten zwei Wochen in diesem Buch haben das Thema Eingabe und Ausgabe (auch als E/A oder I/O für input/output bezeichnet) wiederholt am Rande gestreift. Sie kennen <STDIN> und print für die Standardein- und -ausgabe und wissen, dass man die Dateieingabe über die Befehlszeile, den <>-Operator und die while-Schleife vornimmt, damit jede Zeile automatisch der Variablen $_ zugewiesen wird.

In dem heutigen Kapitel möchte ich Ihr Wissen über die Ein- und Ausgabe vertiefen, Ihnen noch ein bißchen mehr über die Argumentlisten für Skripts erzählen und Ihnen zeigen, wie Sie Daten und Optionen in Ihre Skripts einlesen. Heute erfahren Sie:

Ein- und Ausgabe mit Datei-Handles

Schon zu Beginn dieses Buches, in Kapitel 3, »Weitere Skalare und Operatoren«, habe ich Ihnen im Zusammenhang mit der Standardeingabe und -ausgabe ein wenig über Datei-Handles erzählt. Damals habe ich Ihnen erklärt, dass STDIN und STDOUT eine besondere Art von Datei-Handle darstellen, die sich auf Ein- und Ausgabestreams beziehen, die - statt mit einer Datei - mit der Tastatur beziehungsweise dem Bildschirm verbunden sind. Aber zum Glück läßt sich vieles von dem, was Sie bisher gelernt haben, auch auf Datei-Handles übertragen, die sich auf das Dateisystem beziehen.

In diesem Abschnitt lernen Sie, wie Sie die tückischen Datei-Handles in den Griff bekommen: wie man sie mit der open-Funktion erzeugt, Daten aus ihnen ausliest oder einliest beziehungsweise anhängt und wie man die Dateien wieder schließt, nachdem sie ihren Dienst getan haben. Außerdem werden wir nebenbei wiederholen, was wir bisher über Eingabe und Ausgabe gelernt haben.

Datei-Handles mit open erzeugen

Um Eingaben aus einer Quelle einzulesen oder Ausgaben an ein Ziel zu schreiben, benötigt man einen Datei-Handle. Ein Datei-Handle ist in der Regel mit einer bestimmten Datei auf der Festplatte verbunden, die zum Lesen oder Schreiben von Daten dient. Er kann sich aber auch auf eine Netzwerkverbindung (Socket), eine Pipe (eine Art Verbindung zwischen Standardeingabe und Standardausgabe, auf die wir noch am Tag 18, »Perl und das Betriebssystem«, eingehen) oder sogar bestimmte Hardware-Geräte beziehen. Das Konzept der Datei-Handles sorgt dafür, dass alle diese Operationen in gleicher Weise ausgeführt werden und Sie in gleicher Weise vorgehen können - egal woher die Daten kommen oder wohin sie gehen.

Perl stellt Ihnen drei Standard-Datei-Handles zur Verfügung, von denen Sie bereits zwei kennen: STDIN, STDOUT und STDERR. Die ersten zwei stehen für die Standardeingabe und Standardausgabe (in der Regel Tastatur und Bildschirm). STDERR ist die Standardfehlerausgabe und wird für Fehlermeldungen und andere Nachrichten genutzt, die nicht Teil der eigentlichen Skriptausgabe sind. Normalerweise werden STDERR-Nachrichten wie bei STDOUT auf dem Bildschirm ausgegeben. Nur Programmen, die speziell Gebrauch von der Standardausgabe machen (zum Beispiel Programme auf der anderen Seite von Unix-Pipes), wird hier ein Unterschied auffallen.

Sie brauchen diese Datei-Handles weder zu öffnen noch zu initialisieren. Sie können sie so, wie sie sind, verwenden (wie bereits in den Lektionen der letzten Woche geschehen).

Um aus einer Datei aus- oder einzulesen, müssen Sie zuerst mit Hilfe der Funktion open für die betreffende Operation einen Datei-Handle erzeugen. Die open-Funktion öffnet eine Datei zum Einlesen (Eingabe) oder zum Schreiben (Ausgabe) von Daten und verbindet diese Datei mit einem Datei-Handle (dessen Namen Sie frei vergeben können). Beachten Sie, dass es sich beim Einlesen aus einer Datei und beim Schreiben in eine Datei um getrennte Operationen handelt, die beide einen eigenen Datei-Handle benötigen.

Die open-Funktion übernimmt zwei Argumente: den Namen eines Datei-Handles und die zu öffnende Datei (dazu gehört auch ein besonderer Code, der anzeigt, ob die Datei zum Lesen oder Schreiben geöffnet wird). Hier einige Beispiele:

open(FILE, 'meinedatei');
open(CONFIG, '.scriptconfig');
open(LOG, '>/home/www/logfile');

Den Namen des Datei-Handles können Sie beliebig wählen. Die Konvention sieht jedoch vor, dass er aus Großbuchstaben besteht und Buchstaben, Zahlen oder Unterstriche enthält. Im Gegensatz zu den Variablen muss ein Datei-Handle mit einem Buchstaben beginnen.

Das zweite Argument ist der Name der Datei auf der Festplatte, die mit Ihrem Datei- Handle verbunden werden soll. Ein einfacher Dateiname ohne weitere Pfadangaben wird im aktuellen Verzeichnis gesucht (entweder das, in dem Ihr Skript ausgeführt wird, oder ein anderes Verzeichnis, sollten Sie es gewechselt haben). Alles weitere zum Navigieren von Verzeichnissen erfahren Sie in Kapitel 17, »Umgang mit Dateien und Verzeichnissen«.

Wollen Sie statt einfacher Dateiangaben Pfadnamen verwenden, sollten Sie Vorsicht walten lassen - die Pfadnotation ist von Plattform zu Plattform unterschiedlich. Unter Unix werden die einzelnen Pfade durch Schrägstriche (/) getrennt, wie das letzte unserer obigen Beispiele zeigt.

Auf Windows-Systemen läßt sich problemlos die Standard-DOS-Notation mit Backslash (\) zwischen den Verzeichnissen verwenden, solange Sie daran denken, den ganzen Pfadnamen in einfache Anführungszeichen zu setzen. Zur Erinnerung: Ein Backslash gehört zu den Sonderzeichen in Perl. Wenn Sie also die Anführungszeichen fortlassen, erhalten Sie unter Umständen einen seltsamen Pfad, der keinen Bezug zur Realität hat. Und wenn Sie den String in doppelte Anführungszeichen setzen, müssen Sie vor den Backslash ein Escape-Zeichen (ebenfalls ein Backslash) setzen, um ein korrektes Ergebnis zu erzielen:

open(FILE, 'c:\temp\nummern');  # korrekt
open(FILE, "c:\temp\nummern");
# ooh! enthält einen Tabulator und eine Neue Zeile (\t, \n)
open(FILE "c:\\temp\\nummern"); # korrekt

Da die meisten modernen Windows-Systeme auch Pfadnamen mit Schrägstrichen verstehen, sollten Sie vielleicht besser diese verwenden. Damit erhöhen Sie außerdem gleichzeitig die Portierbarkeit auf Unix-Systeme (falls Ihnen daran gelegen ist).

Beim Macintosh ist das Trennzeichen zwischen den Verzeichnissen ein Doppelpunkt, und der absolute Pfadname beginnt mit der Festplatte oder dem Laufwerksbezeichner (Festplatte, CD-ROM, Diskette und so weiter). Streben Sie die Portierung auf andere Systeme an, sollten Sie sich eine Notiz machen und Ihre Pfadnamen später konvertieren. Hier ein paar Beispiele für die Mac-Syntax:

open(FILE, "Meine Festplatte:Perl:config");
open(BOOKMARKS, "HD:System Folder:Preferences:Netscape:Bookmarks.html");

In jedem dieser Beispiele haben wir ein Datei-Handle geöffnet, um die Eingaben in das Skript einzulesen. Dies entspricht dem Standard. Wollen Sie die Ausgabe in eine Datei zurückschreiben, benötigen Sie dafür ebenfalls einen Datei-Handle, den Sie mit open öffnen - allerdings unter Verwendung eines besonderen Zeichens vor dem Dateinamen:

open(OUT, ">output");

Das Zeichen > zeigt an, dass dieser Datei-Handle zum Schreiben von Daten geöffnet wird. Die gegebene Datei wird geöffnet und der aktuelle Inhalt, sofern vorhanden, gelöscht. (Um das Überschreiben bestehender Dateien zu vermeiden, können Sie in einem Test abfragen, ob eine Datei existiert, bevor Sie sie zum Schreiben von Daten öffnen - im Abschnitt »Datei-Tests« erfahren Sie mehr darüber.)

Wie verfahren Sie, wenn Sie Eingaben von einer Datei auslesen wollen, diese Daten dann bearbeiten und anschließend in dieselbe Datei zurückschreiben wollen? Dann müssen Sie zwei Datei-Handles öffnen: einen Handle zum Einlesen der Eingabe und später den zweiten Handle, um die Datei erneut zu öffnen und die Daten zurückzuschreiben. Lesen und Schreiben sind unterschiedliche Prozesse und bedürfen unterschiedlicher Datei-Handles.

Es sei angemerkt, dass es auch eine Syntax gibt, mit der man ein und dieselbe Datei für das Lesen und Schreiben öffnen kann: "+>filename". Diesen Code können Sie verwenden, wenn Sie die Datei als eine Datenbank verstehen, die Sie nicht als Ganzes einlesen, sondern auf der Festplatte speichern und dann auf diese Datei zum Lesen und Schreiben zugreifen, wenn Sie Daten lesen oder ändern. In diesem Buch beschränke ich mich darauf, einfache Textdateien zu lesen und zu schreiben, so dass die Verwaltung der Daten mit Hilfe von zwei getrennten Datei-Handles weniger verwirrend und leichter ist: ein Handle, um die Daten in den Speicher einzulesen, und ein zweiter Handle, um die Daten wieder in die Datei zu schreiben.

Sie können eine Datei auch zum Anhängen öffnen - dabei bleibt der aktuelle Inhalt der Datei erhalten, und alle Ausgaben über das Ausgabe-Datei-Handle werden an das Ende der Datei angehängt. Um Daten anzuhängen, müssen Sie die Sonderzeichen >> in Ihrem open-Aufruf verwenden:

open(FILE, ">>logdatei");

Die Funktion die

Die Funktion open wird fast immer zusammen mit einem logischen or und einem Aufruf von die auf der anderen Seite aufgerufen:

open(FILE, "dieDatei") or die "dieDatei wurde nicht gefunden\n";

Der Aufruf von die (»sterben«) ist nicht erforderlich, erfolgt aber so häufig in Perl, dass die Kombination fast unzertrennlich wirkt. Wenn Sie die Kombination nicht verwenden, ist es sehr wahrscheinlich, dass Sie irgend jemand, der Ihren Code sieht, eines Tages darauf anspricht und fragt, warum Sie den Befehl fortgelassen haben.

»Öffne diese Datei oder stirb« ist die implizierte Drohung dieser Anweisung, und das ist normalerweise genau das, was Sie auch wollen. Der open-Befehl könnte fehlschlagen - sei es, dass Sie eine Datei zum Lesen öffnen, die nicht existiert, oder dass Ihre Festplatte sich seltsam verhält und die Datei nicht öffnen kann oder dass sonstige unvorhersehbaren Ereignisse eintreten. Normalerweise wollen Sie nicht, dass Ihr Skript einfach weiter ausgeführt wird, wenn etwas so absolut falsch läuft und das Skript nichts zum Einlesen finden kann. Zum Glück liefert die open-Funktion undef zurück, wenn sie die Datei nicht öffnen kann (und 1 wenn es ihr gelungen ist), so dass Sie dieses Ergebnis abfragen können und damit eine Entscheidungshilfe haben, was zu tun ist.

Manchmal kann die Art, in der Sie auf den Fehler reagieren wollen, vom Skript abhängen. In der Regel jedoch wollen Sie einfach nur das Skript mit einer Fehlermeldung verlassen. Und genau dafür sorgt die die-Funktion: Sie beendet automatisch das gesamte Perl-Skript und gibt ihr Argument (eine String-Nachricht) über den Datei-Handle STDERR (normalerweise der Bildschirm) aus.

Wenn Sie ein Neue-Zeile-Zeichen an das Ende der Nachricht setzen, wird Perl diese Nachricht beim Beenden des Skripts ausgeben. Wenn Sie das Neue-Zeile-Zeichen weglassen, gibt Perl als zusätzliche Information die aktuelle Zeilennummer aus: at script.pl line nn. Dabei ist script.pl der Name Ihres Skripts und line nn die Zeile, in der die ausgelöst wurde. Diese Information kann beim späteren Debuggen Ihres Skripts sehr nützlich sein.

Der Befehl die kann aber noch mehr: Die spezielle Perl-Variable $! enthält den letzten Fehler des Betriebssystems (falls Ihr Betriebssystem einen solchen erzeugt hat). Indem Sie die Variable $! in den String für die einbinden, kann Ihre Fehlermeldung unter Umständen noch mehr Informationen liefern, als nur mitzuteilen, dass die Datei nicht geöffnet werden konnte. So kann zum Beispiel die folgende Version von die:

die "Datei konnte nicht geoeffnet werden: $!\n";

in Kombination mit der Variablen folgende Meldung ausgeben: »Datei konnte nicht geoeffnet werden: Zugriff verweigert.« Hier erfährt der Benutzer gleich, dass die Datei nicht geöffnet wurde, weil die Zugriffsberechtigung für die Datei fehlte. Die Verwendung von $! ist immer dann zu empfehlen, wenn Sie die als Antwort auf ein Art von Systemfehler aufrufen.

Obwohl die meist zusammen mit open-Aufrufen verwendet wird, sollte man daraus nicht schließen, dass die nur in diesem Kontext sinnvoll einzusetzen ist. Sie können die (und seine weniger radikale Entsprechung warn) überall dort in Ihrem Skript einsetzen, wo Sie die Ausführung des Skripts abbrechen wollen (oder eine Warnung ausgeben wollen). Weitere Informationen zu die und warn finden Sie in der perlfunc- Manpage.

Eingaben über einen Datei-Handle einlesen

Angenommen Sie haben einen Datei-Handle, und er ist mit einer Datei verbunden, die Sie zum Lesen geöffnet haben. Um Daten über den Datei-Handle einzulesen, verwenden Sie den (Eingabe-)Operator <> zusammen mit dem Namen des Datei- Handles:

$line = <FILE>;

Kommt Ihnen sicherlich bekannt vor, oder? Bei STDIN sind Sie genauso vorgegangen, um eine über die Tastatur eingegebene Zeile einzulesen. Das ist das Tolle an den Datei-Handles - Sie können genau die gleichen Prozeduren für das Lesen aus einer Datei wie für das Lesen von der Tastatur oder von einer Netzwerkverbindung zu einem Server verwenden. Perl macht da keinen Unterschied. Das Verfahren ist immer dasselbe, und alles was Sie bisher über E/A gelernt haben, können Sie auch für die Arbeit mit Dateien verwenden.

In einem skalaren Kontext liest der Eingabeoperator eine einzige Zeile bis zum Neue- Zeile-Zeichen ein:

$line = <STDIN>;
if (<FILE>) { print "weitere Eingaben..." };

Eine besondere Möglichkeit für die Verwendung des Eingabeoperators in einem skalaren Kontext besteht darin, den Operator innerhalb des Tests einer while-Schleife zu verwenden. Damit erreichen Sie, dass bei jedem Schleifendurchlauf jeweils eine Zeile eingelesen und diese Zeile dann der Variablen $_ zugewiesen wird. Die Schleife hört erst auf, wenn das Ende der Eingabe erreicht ist:

while (<FILE>) { 
# ... die Zeilen der Datei (in $_) verarbeiten
}

Die gleiche Notation ist Ihnen bereits häufiger begegnet, allerdings mit leeren Eingabeoperatoren. Die leeren Eingabeoperatoren <> stellen in Perl einen Sonderfall dar. Wie Sie gelernt haben, verwendet man die leeren Eingabeoperatoren, um den Inhalt von Dateien einzulesen, die in der Befehlszeile des Skripts angegeben werden. In einem solchen Fall öffnet Perl die Dateien für Sie und sendet Ihnen deren Inhalt Datei für Datei über den Datei-Handle STDIN. Sie selbst brauchen nichts weiter zu tun. Natürlich könnten Sie auch jede Datei selbst öffnen und einlesen, aber die <>- Operatoren in der while-Schleife stellen eine wirklich praktische Hilfe dar, die den Prozeß erheblich verkürzen.

In einem Listenkontext erreicht man mit den Eingabeoperatoren, dass die gesamte Eingabe auf einmal eingelesen wird, wobei jede Zeile der Eingabe einem Element der Liste zugeordnet wird. Seien Sie vorsichtig, wenn Sie den Eingabeoperator in einem Listenkontext verwenden, da er sich nicht immer so verhält, wie Sie es erwarten. Hierzu einige Beispiele:

@input = <FILE>;   # liest die gesamte Datei nach @input ein;
$input = <FILE>; # liest die erste Zeile der Datei nach $input ein
($input) = <FILE>; # liest die erste Zeile der Datei nach $input ein und
# verwirft den Rest der Datei!
print <FILE>; # gibt den gesamten Inhalt von <FILE> auf dem Bildschirm
# aus

Ausgaben in einen Datei-Handle schreiben

Um Ausgaben in einen Datei-Handle zu schreiben, verwendet man meist die Funktionen print oder printf. (Es gibt zwar noch die write-Funktion, doch wird diese meist in Kombination mit Ausgabeformaten verwendet - ein Thema, das wir erst in Kapitel 20, »Was noch bleibt«, ansprechen werden).

Die Funktion print (wie auch printf) geben ihre Daten standardmäßig an den Datei- Handle STDOUT aus. Um einen anderen Datei-Handle anzusprechen, zum Beispiel um eine Zeile in eine Datei zu schreiben, müssen Sie erst den Datei-Handle zum Schreiben öffnen:

open(FILE, ">$meinedatei") or 
die "Kann die Datei $meinedatei nicht finden\n";

Und dann verwenden Sie print mit dem Datei-Handles als Argument, um Daten in dieser Datei abzulegen:

print FILE "$zeile\n";

Die Funktionen printf und sprintf sind in ihrer Funktionsweise ähnlich; denken Sie nur daran, vor dem Formatierstring und den auszugebenden Werten den Datei-Handle anzugeben, in den die Ausgabe erfolgen soll:

printf(FILE "%d Antworten wurden erfaßt\n", $total / $count);

Einen Punkt dürfen Sie bei der Verwendung von print und printf nicht vergessen: Es steht kein Komma zwischen dem Datei-Handle und der Liste der Dinge, die ausgegeben werden sollen. Dies ist einer der häufigsten Perl-Fehler (der allerdings aufgefangen wird, wenn Sie die Perl-Warnungen eingeschaltet haben). Das Argument des Datei-Handles ist vollkommen getrennt zu sehen von dem zweiten Argument, bei dem es sich um eine Liste von Elementen handelt, die durch Kommata getrennt sind.

Binäre Dateien lesen und schreiben

Bisher haben wir in diesem Buch nur Textdaten gelesen und geschrieben. Aber nicht alle Dateien, mit denen Sie in Perl konfrontiert werden, liegen im Textformat vor. Häufig hat man es mit binären Dateien zu tun. Wenn Sie Perl unter Unix oder auf einem Mac verwenden, macht das keinen großen Unterschied, denn Unix und MacPerl verarbeiten Text- und Binärdateien ohne Probleme. Arbeiten Sie hingegen unter Windows, erhalten Sie unleserliche Ergebnisse, wenn Sie versuchen, eine binäre Datei in einem normalen Perl-Skript zu verarbeiten.

Glücklicherweise kann man dem leicht abhelfen: Die Funktion binmode übernimmt als Argument einen einzelnen Datei-Handle und verarbeitet ihn (aus ihm lesen und in ihn schreiben) in binärer Form:

open(FILE, "meineDatei.exe") or 
die " Datei meineDatei konnte nicht geoeffnet werden: $!\n";
binmode FILE;
while (<FILE>) { # im binären Modus lesen...

Einen Datei-Handle schließen

Wenn Sie mit dem Lesen oder Schreiben der Daten über den Datei-Handle fertig sind, sollte er geschlossen werden. Meist wird Ihnen diese Aufgabe abgenommen, denn wenn die Ausführung Ihres Skripts beendet ist, schließt Perl alle Ihre Datei-Handles für Sie. Und wenn Sie mit open den gleichen Datei-Handle mehrmals hintereinander öffnen (zum Beispiel um einen Datei-Handle, aus dem zuvor gelesen wurde, zum Schreiben zu öffnen), trägt Perl dafür Sorge, dass der Datei-Handle automatisch geschlossen wird, bevor Sie ihn erneut öffnen. Es gehört jedoch zum guten Programmierstil, seine Datei-Handles zu schließen, nachdem sie ihren Dienst erfüllt haben. Dann belegen Sie in Ihrem Skript auch keinen unnötigen Speicher mehr.

Einen Datei-Handle schließt man mit close:

close FILE;

Ein Beispiel: Betreffzeilen extrahieren und sichern

Mailboxen für E-Mails gehören zu den Formaten, die Perl wirklich gut beherrscht. Jede Nachricht folgt einem speziellen Format (auf der Basis des Protokolls RFC822), und alle Nachrichten zusammen werden in einer Mailbox mit ebenfalls speziellem Format gesammelt. Wenn Sie also eine Mailbox sichten und Nachrichten, die bestimmte Kriterien erfüllen, in irgendeiner Weise verarbeiten wollen, dann ist Perl Ihre Sprache. Wie man E-Mails filtert, werden wir uns in Kapitel 21, »Ein paar längere Beispiele«, noch genauer anschauen.

Das Beispiel in diesem Kapitel ist noch sehr einfach: Das Skript übernimmt eine Mailbox als Argument aus der Befehlszeile, liest die Nachrichten einzeln ein, extrahiert alle Zeilen, die mit subject beginnen (englisches Wort, mit dem die Betreff-Zeile der E- Mail beginnt) und legt in dem gleichen Verzeichnis eine Datei namens Betreff an, die eine Liste der Betreffzeilen enthält. Existiert in dem Verzeichnis bereits eine Datei dieses Namens, wird sie überschrieben (im Anschluß an diesen Abschnitt zeige ich Ihnen, wie Sie testen können, ob eine gleichlautende Datei existiert, und dann eine Warnung ausgeben lassen).

Listing 15.1 enthält den (sehr einfachen) Code.

Listing 15.1: Das Skript subject.pl

1:  #!/usr/bin/perl -w
2: use strict;
3:
4: open(OUTFILE, ">Betreff") or
die " Datei Betreff konnte nicht geoeffnet werden: $!\n";
5:
6: while (<>) {
7: if (/^Subject:/) {
8: print OUTFILE $_;
9: }
10: }
11: close OUTFILE;

Ein kurzes Skript, werden Sie denken, das kaum als Beispiel taugt. Aber genau das ist der springende Punkt: Zum Auslesen von Daten aus Dateien und zum Schreiben in Dateien verwenden Sie die gleichen Techniken wie für die Standardeingabe und - ausgabe. Auf zwei Dinge möchte ich Ihre Aufmerksamkeit lenken: Erstens, Zeile 4 öffnet die Datei Betreff zum Schreiben (beachten Sie dabei das Zeichen > am Anfang des Dateinamens) und zweitens, Zeile 8 übergibt unsere Ausgabe dem gleichen Datei- Handle OUTFILE und nicht der Standardausgabe.

Dieses Skript erzeugt zwar keine sichtbare Ausgabe, aber wenn Sie es auf einer Datei mit abgespeicherten E-Mails ausführen und dann die Datei Betreff betrachten, finden Sie dort Zeilen wie die folgenden (mein Beispiel hier ist das Ergebnis einer Analyse meiner »Business-Mail«):

Subject: FREE SOFTWARE TURN$ COMPUTER$ INTO CA$H MACHINE$!!
Subject: IBM 33.6 PCMCIA Modem $89.00
Subject: 48 MILLION Email Leads $195 + BONUSES
Subject: Re: E-ALERT: URGENT BUY RECOMMENDATION
Subject: Make $2,000 - $5,000 per week -NOT MLM
Subject: Email your AD to 57 MILLION People for ONLY $99
Subject: SHY?.....................................
Subject: You Could Earn $100 Every Time the Phone Rings!!

Dateitests

Das Öffnen von Dateien zum Einlesen oder Hineinschreiben ist gut und schön, wenn Sie die Dateien, mit denen Sie arbeiten, kennen - zum Beispiel wissen, dass sie alle vorhanden sind oder dass Sie nicht Gefahr laufen, wichtige Daten zu überschreiben. Manchmal jedoch wollen Sie in Perl die Eigenschaften einer Datei prüfen, bevor Sie sie öffnen, oder eine Datei in Abhängigkeit von ihren diversen Eigenschaften in der einen oder anderen Weise verarbeiten.

Perl kennt eine (ziemlich umfangreiche) Reihe von Tests für die verschiedenen Eigenschaften von Dateien. Mit Hilfe dieser Tests läßt sich leicht feststellen, ob eine Datei bereits existiert, ob Sie Daten enthält, zu welcher Art von Datei sie gehört oder wie alt sie ist (»1996 war ein gutes Jahr für binäre Dateien, nicht wahr?«). Diese Tests erinnern alle an Schalter (-e, -R, -o und so weiter), sie sollten aber nicht mit diesen verwechselt werden (die Ähnlichkeit ist eine Folge davon, dass sie alle ihren Ursprung in der Unix-Shell-Skriptsprache haben).

Tabelle 15.1 gibt einen Überblick über einige der nützlicheren Dateitests. In der perlfunc-Manpage sind unter dem Eintrag -X alle Tests aufgeführt (allerdings sind nicht alle Optionen für alle Plattformen verfügbar).

Jeder dieser Tests kann als Argument einen Dateinamen oder einen Datei-Handle übernehmen; beides ist möglich (wenn Sie jedoch überprüfen wollen, ob es eine bestimmte Datei gibt oder nicht, werden Sie mit Sicherheit den Dateinamen übergeben, um den Test vor dem Aufruf von open durchzuführen).

Test

Was er bewirkt

-d

Handelt es sich bei der Datei um ein Verzeichnis?

-e

Existiert die Datei?

-f

Ist die Datei eine einfache Datei (und kein Verzeichnis, Link oder Netzwerkverbindung)?

-l

Ist die Datei ein Link (nur Unix)?

-r

Kann die Datei gelesen werden (von Benutzer oder Gruppe unter Unix)?

-s

Wieviel Byte umfaßt die Datei?

-t

Ist das Datei-Handle offen für STDIN (oder ein anderes tty-Gerät unter Unix)?

-w

Kann in die Datei geschrieben werden (von Benutzer oder Gruppe unter Unix)?

-x

Handelt es sich bei der Datei um eine ausführbare Datei?

-z

Ist die Datei vorhanden, jedoch leer?

-A

Wieviel Zeit ist seit dem letzten Zugriff verstrichen (in Sekunden)?

-B

Handelt es sich um eine binäre Datei (keine Textdatei)?

-M

Wieviel Zeit ist seit der letzten Änderung verstrichen (in Sekunden)?

-T

Handelt es sich um eine Textdatei (keine binäre Datei)?

Tabelle 15.1: Dateitests

Fast alle Tests liefern entweder wahr (1) oder falsch (»«) zurück. Nur -e, -s, -M und -A liefern andere Werte. Der Test -e liefert undef zurück, wenn die Datei nicht existiert, -s liefert die Anzahl der Bytes (Zeichen) in der Datei, und die Zeitoperatoren -M und -A liefern die Anzahl der Sekunden, die seit der letzten Änderung beziehungsweise dem letzten Zugriff verstrichen sind.

Angenommen Sie wollten das Skript subject.pl dahingehend ändern, dass für den Fall, dass eine Datei Betreff bereits existiert, der Benutzer mit einer Eingabeaufforderung selber entscheiden kann, ob die Datei überschrieben werden soll oder nicht. Anstelle des bisherigen einfachen Aufrufs von open könnten Sie einen Test durchführen, um sicherzugehen, dass die Datei überhaupt existiert, und wenn ja, den Benutzer entscheiden lassen, ob diese überschrieben werden soll oder nicht. Schauen Sie sich dazu den folgenden Code an:

if (-e 'Betreff') {
print 'Datei existiert bereits. Überschreiben (J/N)? ';
chomp ($_ = <STDIN>);
while (/[^jn]/i) {
print 'J oder N, bitte: ';
chomp ($_ = <STDIN>);
}
if (/n/i) { die "Die Datei Betreff existiert bereits; Abbrechen.\n"; }
}

In diesem Beispiel sehen Sie eine andere Anwendungsmöglichkeit für die Funktion die - diesmal ohne die Funktion open. Wenn der Benutzer die Frage zum Überschreiben der Datei mit N beantwortet, könnten Sie das Skript einfach verlassen. Die die-Funktion beendet das Skript ausdrücklich und gibt eine entsprechende Nachricht aus.

Mit @ARGV und Skriptargumenten arbeiten

Einen Aspekt bei der Ausführung von Perl-Skripten, den ich in den letzten Tagen etwas vernachlässigt habe, betrifft den Umgang mit Befehlszeilenargumenten. Sie haben schon ein wenig über Perls eigene Schalter (-e, -w und so weiter) erfahren, wie aber gehen Sie vor, wenn Sie solche Schalter oder Argumente Ihren eigenen Skripten übergeben wollen - wie kann man diese verarbeiten? Dieser Abschnitt behandelt im besonderen folgende Themen: Skriptargumente im allgemeinen und den Einsatz von Skriptschaltern.

Die Anatomie von @ARGV

Wenn Sie ein Perl-Skript nicht nur mit dem Namen des Skripts, sondern mit weiteren Argumenten aufrufen, werden diese Argumente in einer besonderen globalen Liste, der @ARGV-Liste, gespeichert (für Mac-Droplets enthält @ARGV die Namen der Dateien, die auf das Droplet gezogen wurden). Sie können dieses Array genauso verarbeiten wie jede andere Liste in Ihrem Perl-Skript. Sehen Sie im folgenden ein Codefragment, das lediglich die Argumente, mit denen das Skript aufgerufen wurde, ausgibt, und zwar ein Argument pro Zeile:

foreach my $arg (@ARGV) {
print "$arg\n";
}

Wenn Ihr Skript ein Konstrukt wie while (<>) verwendet, zieht Perl den Inhalt der @ARGV-Liste als Dateinamen zum Öffnen und Lesen heran (stehen keine Dateien in @ARGV, versucht Perl, von der Standardeingabe zu lesen). Mehrere Dateien werden hintereinander geöffnet und gelesen, als ob es eine einzige große Datei wäre.

Wenn Sie mehr Kontrolle über den Inhalt der Dateien, die Sie in Ihr Skript einlesen, haben wollen, können Sie die Namen der zu öffnenden und zu lesenden Dateien aus der @ARGV-Liste auslesen. Das Auswerten von @ARGV bietet sich auch dann an, wenn Sie nach bestimmten Argumenten suchen - zum Beispiel einer Konfigurationsdatei und einer Datendatei. Wollen Sie hingegen den Inhalt einer beliebigen Anzahl von Dateien bearbeiten, ist es praktischer, die Abkürzung <> zu verwenden. Und wenn Sie einen bestimmten Satz von Argumenten erwarten und kontrollieren wollen, wie diese verarbeitet werden sollen, lesen Sie die Dateien von @ARGV aus, und bearbeiten Sie sie einzeln.

Im Gegensatz zu argv, wie es C und Unix kennen, enthält die @ARGV-Liste von Perl nur die Argumente und nicht den Namen des Skripts selber ($ARGV[0] enthält das erste Argument). Um den Namen des Skripts zu ermitteln, können Sie die spezielle Variable $0 verwenden.

Skriptschalter und Spaß mit Getopt

Ein typischer Anwendungsbereich für die Skript-Befehlszeile ist die Übergabe von Schaltern an ein Skript. Schalter sind Argumente, die mit einem Gedankenstrich beginnen (-a, -b, -c) und in der Regel dazu dienen, das Verhalten des Skripts zu steuern. Manchmal bestehen Sie nur aus einem Buchstaben (-s), manchmal sind sie zu Gruppen zusammengefaßt (-abc), und manchmal ist ihnen ein Wert oder Argument zugeordnet (-o ausgabe.txt).

Sie können ein Skript mit jedem beliebigen Schalter aufrufen. Die Schalter werden, wie alle anderen Argumente auch, als Elemente im @ARGV-Array aufgenommen. Wenn Sie das Array @ARGV mit <> bearbeiten, müssen Sie die Schalter im Array erst loswerden, bevor Sie irgendwelche Daten auslesen - sonst würde Perl davon ausgehen, dass es sich bei -s um einen Dateinamen handelt. Um alle Schalter, die über das ganze @ARGV-Array verstreut sind, zu bearbeiten und zu entfernen, könnten Sie mühselig das Array durchgehen und versuchen herauszufinden, welche Elemente davon Optionen und welche Optionen mit dazugehörigen Argumenten sind. Als Endergebnis bliebe dann eine Liste der eigentlichen Dateinamen. Sie könnten sich aber auch des Moduls Getopt bedienen und sich damit die Arbeit abnehmen lassen.

Das Modul Getopt, das als Teil der Standard-Modul-Bibliothek zusammen mit Perl ausgeliefert wird, dient der Verwaltung der Skriptschalter. Eigentlich handelt es sich um zwei Module: Getopt::Std für die Bearbeitung von Schaltern, die aus einem Buchstaben bestehen (-a, -d, -odatei und so weiter), und Getopt::Long, das fast alle erdenklichen Optionen akzeptiert, einschließlich Optionen, die aus mehreren Buchstaben bestehen (-sde), oder Optionen im GNU-Stil mit doppeltem Bindestrich (--help, --size und so weiter).

In diesem Abschnitt bespreche ich das Modul Getopt::Std für die einfachen Optionen. Wenn Sie für komplexere Optionen das Modul Getopt::Long einsetzen möchten, sollten Sie auf die Dokumentation zu diesem Modul zurückgreifen (Details finden Sie in der perlmod-Manpage).

Für eine erfolgreiche Arbeit müssen Sie das Modul Getopt::Std, wie jedes andere Modul auch, in Ihr Skript importieren:

use Getopt::Std;

Durch den Import von Getopt::Std erhalten Sie zwei Funktionen: getopt und getopts. Diese Funktionen werden benötigt, um die Schalter aus Ihrem @ARGV-Array herauszuziehen und für jeden dieser Schalter in Ihrem Skript eine Skalarvariable zu setzen.

Das Modul Getopt funktioniert in der Anwendungsversion von MacPerl nicht ordnungsgemäß, da es in MacPerl keine einfache Möglichkeit gibt, Befehlszeilenschalter einzulesen. Im Abschnitt »Skriptschalter auf dem Macintosh« finden Sie Hinweise, wie Sie dieses Problem in MacPerl umgehen.

getopts

Beginnen wir mit der Funktion getopts, die einbuchstabige Schalter mit oder ohne Werte definiert und bearbeitet. getopts übernimmt ein einziges String-Argument, das die Zeichen für die Schalter enthält, die von Ihrem Skript akzeptiert werden sollen. Argumente, die Werte übernehmen, müssen von einem Doppelpunkt (:) gefolgt werden. Groß- und Kleinbuchstaben machen einen Unterschied und definieren verschiedene Schalter. Schauen wir uns ein Beispiel an:

getopts('abc');

Das in diesem Beispiel verwendete Argument 'abc' bearbeitet die Schalter -a, -b oder -c in einer beliebigen Reihenfolge und ohne zugeordnete Werte. Die Schalter können auch zusammengefaßt werden: -ab oder -abc lassen sich genauso verwenden wie die einzelnen Schalter. Noch ein Beispiel:

getopts('ab:c');

Hier kann der Schalter -b einen Wert übernehmen, der auf der Perl-Befehlszeile direkt nach dem Schalter folgen muss:

% meinskript.pl -b 10

Das Leerzeichen hinter dem Schalter ist nicht erforderlich. -b10 läßt sich genauso schreiben wie -b 10. Sie können die Schalter sogar verschachteln, solange der Wert nur nach dem korrekten Schalter erscheint:

% meinskript.pl -acb10 # OK
% meinskript.pl -abc10 # falsch, b und 10 gehören zusammen

Für jeden in getopts definierten Schalter erzeugt getopts einen Skalarvariablen- Schalter mit dem Namen $opt_x, wobei x der Buchstabe des Schalters ist (in unserem Beispiel würde getopts drei Variablen $opt_a, $opt_b und $opt_c erzeugen). Der Anfangswert jeder Skalarvariablen ist 0. Wenn der Schalter in den Argumenten zu dem Skript enthalten ist (in @ARGV steht), setzt getopts den Wert der zugeordneten Variable auf 1. Erwartet ein Schalter einen Wert, weist getopts den Wert aus @ARGV der Skalarvariablen für die Option zu. Danach werden der Schalter und sein dazugehöriger Wert aus dem Array @ARGV gelöscht. Nachdem getopts seine Bearbeitung abgeschlossen hat, ist Ihr @ARGV entweder leer oder enthält die restlichen Argumente in Form von Dateinamen, die Sie dann mit den Datei-Handles oder mit <> verarbeiten können.

Nachdem getopts seine Aufgabe erledigt hat, steht Ihnen für jeden Schalter eine Variable zur Verfügung, die entweder den Wert 0 hat (für einen unbenutzten Schalter), 1 (für einen benutzten Schalter) oder einen bestimmten Wert (für einen Schalter, der einen Wert erfordert). Sie können diese Werte abfragen und Ihr Skript, je nachdem mit welchem Schalter es aufgerufen wurde, verschiedene Operationen ausführen lassen:

if ($opt_a) {  # -a wurde verwendet
...
}
if ($opt_b) { # -b wurde verwendet
...
}

Wenn beispielsweise der Aufruf Ihres Skripts wie folgt aussieht:

% skript.pl -a

wird getopts('abc') die Variable $opt_a auf 1 setzen. Würde das Skript folgendermaßen aufgerufen:

% skript.pl -a -R

wird $opt_a auf 1 gesetzt und der Schalter -R einfach gelöscht, ohne dass eine Variable gesetzt wurde. Bei folgendem Aufruf des Skripts:

% skript.pl -ab10

und gleichzeitigem Aufruf von getopts mit

getopts('ab:c');

wird $opt_a auf 1 gesetzt und $opt_b auf 10.

Denken Sie daran, dass Perl bei Verwendung des Befehls use strict die plötzlich auftauchenden Variablen $opt_ monieren wird. Das läßt sich jedoch vermeiden, indem Sie diese Variablen im voraus mit use vars wie folgt deklarieren:

use vars qw($opt_a $opt_b $opt_c);

Fehlerbehandlung mit getopts

Es gilt zu beachten, dass getopts das Array @ARGV der Reihe nach ausliest und die Bearbeitung unterbricht, wenn es auf ein Element stößt, das nicht mit einem Gedankenstrich (-) beginnt oder das keinen Wert für eine vorausgehende Option darstellt. Das bedeutet, dass Sie beim Aufruf eines Perl-Skripts zuerst die Optionen und dann die anderen Argumente aufführen sollten. Andernfalls droht Ihnen, dass Optionen unbearbeitet bleiben und Sie Fehler erhalten, weil versucht wird, Dateien zu lesen, die gar keine Dateien sind. Sie können Ihr Skript natürlich auch so schreiben, dass sichergestellt ist, dass @ARGV leer ist, nachdem getopts abgearbeitet ist, oder dass die übriggebliebenen Argumente nicht mit einem Gedankenstrich beginnen.

Grundsätzlich sind die von getopts definierten Schalter die einzigen Schalter, die Ihr Skript akzeptiert. Wenn Sie ein Skript mit einem Schalter aufrufen, der nicht in dem Argument zu getopts definiert ist, gibt getopts einen Unknown option-Fehler aus (»unbekannte Option«), löscht die Option aus @ARGV und liefert falsch zurück. Dieses Verhalten können Sie sich zunutze machen, um sicherzustellen, dass Ihr Skript korrekt aufgerufen wird, und um im anderen Falle mit einer Meldung das Skript zu beenden. Dazu muss der Aufruf von getopts lediglich innerhalb eine if-Anweisung erfolgen:

if (! getopts('ab:c')) {
die "Aufruf: meinskript -a -b:c datei\n";
}

Bedenken Sie auch, dass im Falle, dass getopts die Bearbeitung Ihrer Schalter aufgrund eines Fehlers mittendrin abbricht, alle Schaltervariablen, die zuvor gesetzt wurden, ihre Werte beibehalten, auch die inkorrekten Werte. Je nachdem wie robust Ihre Argumentenprüfung sein soll, können Sie diese Werte auch für den Fall überprüfen, dass getopts falsch zurückliefert (oder ganz abbricht).

getopt

Die Funktion getopt gleicht der Funktion getopts insofern, als beide ein Stringargument übernehmen, in dem die Schalter definiert sind, jedem dieser Argumente eine Variable $opt_ zuweisen und diese dann aus @ARGV entfernen. Zwischen getopt und getopts gibt es jedoch drei wesentliche Unterschiede:

Angenommen Ihr Aufruf an getopt lautet wie folgt:

getopt('abc');

Diese Funktion geht davon aus, dass Ihr Skript mit einer beliebigen Kombination der drei Schalter -a, -b oder -c aufgerufen wird, wobei jedem Schalter ein Wert zugewiesen wird. Wird das Skript mit Schaltern aufgerufen, die keinen Wert haben, sollten Sie von getopt keine Warnung erwarten - statt dessen weist es der Variablen für den Schalter freudig das nächste Element in @ARGV zu, auch wenn das nächste Element ein anderer Schalter oder ein Dateiname ist, der als Datei ausgelesen werden sollte. Es obliegt Ihnen, herauszufinden, ob die Werte korrekt sind oder ob das Skript mit dem falschen Satz an Argumenten aufgerufen wurde.

Im Grunde genommen liegt der Hauptunterschied zwischen getopt und getopts darin, dass Sie bei getopt Ihre Optionen nicht deklarieren müssen, was jedoch die Fehlerbehandlung wesentlich erschwert. Ich ziehe in den meisten Fällen getopts vor, da ich so unnötiges Wertetesten vermeiden kann.

Skriptschalter auf dem Macintosh

Auf dem Mac gibt es keine Skript-Befehlszeile und deshalb können MacPerl-Skripts auch keine Befehlszeilenschalter annehmen, wie das bei Unix oder bei Windows- Skripten möglich ist (über Droplets können sie jedoch Dateinamenargumente entgegennehmen). Das Problem läßt sich auf mehreren Wegen umgehen: So könnten Sie zum Beispiel am Anfang Ihres Skripts zur Eingabe von Schalter auffordern und die Eingabe in @ARGV speichern (so dass Sie getopt verwenden können, um die Schalter innerhalb Ihres Skripts zu verarbeiten). Das folgende Codefragment zeigt hierzu ein Beispiel:

print "Schalter eingeben: ";
chomp($in = <STDIN>);
@ARGV = split(' ', $in);

Eine etwas ausgefeiltere Version, die das MacPerl-Modul und ein Dialogfeld verwendet, finden Sie als Teil von MacPerl FAQ unter http://www.perl.com/CPAN- local/doc/FAQs/mac/MacPerlFAQ.html:

if( $MacPerl::Version =~ /Application$/ ) {
# Ausführung als Anwendung
local( $cmdLine, @args );
$cmdLine = &MacPerl::Ask( "Befehlszeilenargumente eingeben:" );
require "shellwords.pl";
@args = &shellwords( $cmdLine );
unshift( @ARGV, @args );
}

Der wahre Mac-Benutzer umgeht die Befehlszeilenschalter, indem er MacPerl-Module verwendet, um eigene Dialoge zu erstellen, in denen die Schalter als echte Benutzerschnittstellenelemente implementiert werden. Ein paar einfache Dialoge werden wir in Kapitel 18 besprechen. Weitere Informationen zu den Dialogen finden Sie in der MacPerl-Dokumentation.

Wenn Sie die MPW-Version von MacPerl verwenden, können Sie das bisher Gesagte vergessen, denn hier haben Sie eine Befehlszeile. Also nutzen Sie sie!

Ein weiteres Beispiel

Im folgenden sehen Sie ein einfaches Beispiel (Listing 15.2), das eine Datei auf unterschiedliche Arten bearbeitet. Wie die Datei konkret verarbeitet wird, hängt von den verwendeten Schaltern ab.

Listing 15.2: Das Skript schalter.pl

1:  #!/usr/bin/perl -w
2: use strict;
3: use Getopt::Std;
4: use vars qw($opt_r $opt_l $opt_s $opt_n);
5:
6: if (! getopts('rlsn')) {
7: die "Aufruf: schalter.pl -rlsn\n";
8: }
9:
10: my @file = <>;
11:
12: if ($opt_s) {
13: @file = sort @file;
14: }
15:
16: if ($opt_n) {
17: @file = sort {$a <=> $b} @file;
18: }
19:
20: if ($opt_r) {
21: @file = reverse @file;
22: }
23:
24: my $i = 1;
25: foreach my $line (@file) {
26: if ($opt_l) {
27: print "$i: $line";
28: $i++;
29: } else {
30: print $line;
31: }
32: }

Dieses Skript verwendet nur einfache Schalter ohne Werte (beachten Sie den Aufruf von getopts in Zeile 6; es stehen keine Doppelpunkte nach den Optionen). Die Schalter lauten -r, um den Inhalt der Datei umzudrehen, -s, um die Zeilen der Datei alphabetisch zu sortieren, -n, um die Zeilen numerisch zu sortieren, und -l, um die Zeilennummer auszugeben. Sie können die Optionen in der Befehlszeile kombinieren, auch wenn manche Kombinationen nicht besonders sinnvoll sind (-sn sortiert die Datei und sortiert sie dann erneut numerisch).

In Zeile 4 werden die Variablen vorab deklariert, so dass Sie nicht plötzlich aus dem Nichts auftauchen, wenn sie mit getopts erzeugt werden (und Beschwerden durch use strict auslösen).

Der Test in den Zeilen 6 bis 8 stellt sicher, dass das Skript mit den richtigen Optionen aufgerufen wird. Rutscht dabei eine undefinierte Option mit durch (zum Beispiel -a oder -x), dann bricht das Skript ab und gibt eine entsprechende Meldung aus.

Anschließend wird in verschiedenen if-Anweisungen geprüft, ob die Variablen $opt_r, $opt_l, $opt_s und $opt_n existieren, und dann werden je nach aufgerufener Option auf der Befehlszeile verschiedene Operationen durchgeführt. Alle Argumente, die keine Schalter sind, verbleiben nach dem Aufruf von getopts in @ARGV und werden mit dem Operator <> in Zeile 10 in das Skript eingelesen.

Vertiefung

In dieser Lektion habe ich Ihnen die Grundlagen der Ein- und Ausgabe vermittelt und gezeigt, wie man Dateisysteme verwaltet. Das, was Sie hier gelernt haben, sollte sich auf alle Ihre Perl-Programme, die Dateien und Befehlszeilenargumente verwenden, übertragen lassen. In Kapitel 17 werden wir einen eingehenderen Blick auf das Dateisystem selbst werfen. In dem Vertiefungsabschnitt dieser Lektion möchte Ihnen aufzeigen, wo Sie weitere Informationen und Details zu fortgeschritteneren Aspekten der Ein- und Ausgabe und dem Umgang mit dem Dateisystem finden.

Alle vordefinierten Perl-Funktionen sind, wie ich bereits gesagt habe, in der perlfunc- Manpage dokumentiert. Von Nutzen können aber auch die FAQs zu Dateien und Formaten in der Hilfsdokumentation perlfaq sein.

Mehr zu open und den Datei-Handles

Hier noch einige weitere Kurzschreibweisen und Eigenheiten der open-Funktion:

Sie können den Dateinamen beim Aufruf der open-Funktion weglassen, wenn - und nur in diesem Fall - einer Skalarvariablen, die den gleichen Namen wie das Datei- Handle trägt, bereits der Name der zu öffnenden Datei zugewiesen wurde. Zum Beispiel:

$FILE = "meinedatei.txt";
open(FILE) or die "Datei $FILE kann nicht geöffnet werden: $!\n";

Dies kann für Dateinamen, die geöffnet oder erneut geöffnet werden müssen, nützlich sein. Sie können die Variable zu Beginn Ihres Skripts setzen und dann den Dateinamen immer wieder verwenden.

Im Gegensatz zu dem, was ich Ihnen zu Beginn dieses Kapitels gesagt habe, können Sie eine Datei auch gleichzeitig zum Lesen und Schreiben öffnen. Verwenden Sie dazu das Sonderzeichen +> vor den Dateinamen:

open(FILE, "+>diedatei") or die "Datei kann nicht geöffnet werden: $!\n";

Da dies jedoch oft nur Verwirrung stiftet, ziehe ich es vor, getrennte Datei-Handles zu verwenden und das Lesen und Schreiben als getrennte Operationen zu betrachten.

Dateinamen, die mit einem Pipe-Zeichen (|) beginnen, fungieren als Befehl, und die Ausgabe wird über die Befehls-Shell Ihres Systems an diesen Befehl geleitet.

Die Fülle an Möglichkeiten, die Ihnen open bietet, finden Sie detailliert in der Hilfsdokumentation perlfunc-Manpage beschrieben.

Weitere Dateifunktionen

Tabelle 15.2 enthält weitere vordefinierte dateibezogene Funktionen, die ich in dieser Lektion noch nicht beschrieben habe.

Funktion

Was sie bewirkt

eof

Liefert wahr zurück, wenn die nächste Zeileneingabe am Ende der Zeile erfolgt

eof()

Unterscheidet sich von eof; diese Version findet das Ende der letzten Datei für die Datei-Eingabe mit <>

lstat

Zeigt Informationen zu Links an

pack

Datenausgabe erfolgt in einer binären Struktur

select

Ändert das Standard-Datei-Handle für die Ausgabe (wird normalerweise für Formate verwendet, siehe Kapitel 20)

stat

Gibt verschiedene Informationen über eine Datei oder einen Datei-Handle aus

truncate

Löscht den Inhalt einer Datei oder eines Datei-Handle

unpack

Dateneingabe erfolgt von einer binären Struktur

Tabelle 15.2: Weitere E/A-Funktionen

Eingabe/Ausgabe für Experten

Die bisher in diesem Kapitel beschriebenen Eingabe- und Ausgabetechniken umfassen die einfache, zeilenorientierte gepufferte Ein- und Ausgabe über Datei-Handles, der Standard-Ein-/-Ausgabe oder der Standardfehlerausgabe. Wenn Sie an den fortgeschrittenen Möglichkeiten der Ein- und Ausgabe interessiert sind, sollten Sie die Dokumentation der verschiedenen anderen E/A-Funktionen, die Perl für Sie bereithält, lesen. Eine Übersicht finden Sie in Tabelle 15.3.

Funktion

Was sie bewirkt

fcntl

Datei-Steuerung (Unix-Funktion fctrl(2))

flock

Datei sperren (Unix-Funktion flock(2))

getc

Liest nächstes Byte ein

ioctl

TTY-Steuerung (Unix-Systemaufruf ioctl(2))

read

Einlesen einer bestimmten Anzahl von Bytes (fread(2))

rewinddir

Setzt die aktuelle (Eingabe-)Position an den Anfang des Verzeichnis-Handles

seek

Positioniert den Dateizeiger auf eine bestimmte Stelle in einer Datei (entspricht fseek() in C)

seekdir

Entspricht seek für Verzeichnis-Handles

select

Bereitet die Dateideskriptoren zum Einlesen vor (entspricht dem Unix-Befehl select(2); nicht zu verwechseln mit select zum Setzen des Standard-Datei-Handles)

syscall

Aufruf eines Unix-Systemaufrufs (syscall(2))

sysopen

Öffnet einen Datei-Handle mit Modus und Zugriffsberechtigungen

sysread

Liest mit Hilfe von read(2) eine bestimmte Anzahl von Bytes ein

syswrite

Schreibt mit Hilfe von write(2) eine bestimmte Anzahl von Bytes in den Datei-Handle

tell

Liefert die aktuelle Position des Dateizeigers

telldir

Entspricht tell für Verzeichnis-Handles

write

Schreibt einen formatierten Datensatz in einen Ausgabedatei-Handle (siehe Tag 20); write ist nicht das Gegenstück zu read

Tabelle 15.3: Weitere E/A-Funktionen

Außerdem gibt es noch das Modul POSIX, das weitere Möglichkeiten für fortgeschrittene E/A-Operationen bietet (leider läßt sich dieses Modul nur unter Unix einsetzen). Weitere Informationen zu POSIX finden Sie in der perlmod-Manpage.

DBM-Dateien

Perl bietet auch Unterstützung für Berkeley-Unix-DBM-Dateien (Datenbankdateien). Diese Dateien sind in der Regel kleiner und schneller anzusprechen als reine textbasierte Datenbanken. Weitere Informationen zu DBM finden Sie unter dem DB_File-Modul, der tie-Funktion und den diversen Tie-Modulen (Tie::Hash, Tie::Scalar und so weiter).

CPAN enthält eine Reihe von Modulen für die Arbeit mit Datenbanken - sei es, dass Sie selbst Datenbanken erstellen wollen, sei es, dass Sie Schnittstellen und Treiber für den Zugriff auf kommerzielle Datenbanken wie Oracle und Sybase benötigen. Für letztgenannte seien die Pakete DBD (Datenbanktreiber) und DBI (Datenbankschnittstelle) der Perl Database Initiative wärmstens empfohlen.

Zeitmarkierungen

Dateien und Verzeichnisse können mit sogenannten Zeitmarkierungen versehen werden. Dabei handelt es sich um Angaben, wann die Datei erzeugt, geändert oder zuletzt darauf zugegriffen wurde. Mit Hilfe von Dateitests (-M für Änderungen, -A für Zugriff und -C für Änderungen an den inode-Informationen1) können Sie die Zeitmarkierungen überprüfen, mit Hilfe der stat-Funktion erhalten Sie detailliertere Informationen über die Zeitmarkierungen, und mit der utime-Funktion läßt sich die Zeitmarkierung einer Datei ändern. Das Verhalten der Tests und Funktionen kann dabei von Plattform zu Plattform variieren.

Alle Zeitangaben erfolgen in Sekunden, gemessen ab einem bestimmten Zeitpunkt; für Unix und Windows ist das der 1. Januar 1970, für den Macintosh der 1. Januar 1904. Zum Decodieren und Ändern von Zeitmarkierungen können auch die Funktionen time, gmtime, localtime und die Module Time::Local nützlich sein.

Zusammenfassung

In diesem Kapitel haben wir das, was Sie bisher über die Ein- und Ausgabe gelernt haben, vertieft und ergänzt. Dabei haben wir die Techniken, die beim Lesen aus der Standardeingabe und beim Schreiben in die Standardausgabe zur Anwendung kamen, auf das Einlesen und Schreiben von Dateien übertragen.

Zu Beginn standen Dateien und Datei-Handles im Zentrum unserer Betrachtung. Ich habe Ihnen gezeigt, wie Sie die Funktion open verwenden, um eine Datei zu öffnen und ein Datei-Handle zu erzeugen, über das Sie aus dieser Datei lesen oder in die Datei schreiben können. In Zusammenhang mit open haben Sie die Funktion die kennengelernt, die das Skript beendet und dabei eine Fehlernachricht an die Standardfehlerausgabe schickt.

Im weiteren Verlauf der Lektion sprachen wir über Skript-Argumente und Schalter: was passiert, wenn Sie ein Skript mit Argumenten aufrufen (sie werden in @ARGV abgelegt), und wie Sie vorgehen, um diese Argumente zu bearbeiten. Enthalten diese Argumente Schalter, bearbeiten Sie sie am besten mit dem Modul Getopt::Std. Damit können Sie Schalter für Ihr Skript definieren und bearbeiten und dann mit Hilfe spezieller Variablen prüfen, ob diese Schalter überhaupt existieren.

Unter anderem haben Sie folgende Funktionen in diesem Kapitel kennengelernt:

Fragen und Antworten

Frage:
Ich versuche, eine Datei zum Schreiben zu öffnen, aber es wird immer wieder die Funktion die ausgelöst, und ich kann mir nicht erklären, warum. Das Verzeichnis erlaubt den Lesezugriff, die Datei existiert noch nicht - es gibt keinen Grund, warum hier etwas schieflaufen sollte.

Antwort:
Haben Sie daran gedacht, dass >-Zeichen vor den Dateinamen zu setzen? Sie brauchen dieses Zeichen, um Perl mitzuteilen, dass in die Datei geschrieben werden soll. Andernfalls geht Perl davon aus, dass aus dieser Datei ausgelesen wird, und wenn es diese Datei dann nicht findet, ist es auch nicht in der Lage, sie zu öffnen.

Frage:
Ich möchte meine Datei mit einer Subroutine öffnen und dann den Datei-Handle an andere Subroutinen weiterreichen. Aber wenn ich das versuche, funktioniert es nicht. Warum?

Antwort:
Weil es so einfach nicht geht. Man kann zwar mit Typeglobs einige trickreiche Kniffe anwenden, um Symbolnamen zwischen Subroutinen weiterzureichen, doch ist dies ein Gebiet, das wieder seine ganz eigenen Probleme birgt. Am besten übergibt man Datei-Handles, indem man das Modul FileHandle nutzt, den Datei-Handle als Objekt erzeugt und dieses Objekt dann zwischen den Subroutinen weiterreicht.

Frage:
Ich versuche eine einfache textbasierte Datenbank in Perl zu lesen. Ich kenne das Format der Datei und weiß, wie man es dekodieren muss, um etwas damit anfangen zu können. Trotzdem ist die Eingabe, die ich erhalte, absolut unverständlich. Wo liegt der Fehler?

Frage:
Ich arbeite mit MacPerl. Jetzt habe ich eine Datei mit Zahlen von einem Unix- System vor mir liegen, und wenn ich sie in mein Skript einlese, erhalte ich als Ergebnis einen einzigen großen String anstatt eines Arrays von einzelnen Strings. MacPerl ignoriert anscheinend die Neue-Zeile-Zeichen. Wie gehe ich weiter vor?

Antwort:
Das haben Sie ganz richtig diagnostiziert: MacPerl ignoriert die Neue-Zeile- Zeichen. Unix- und Macintosh-Systeme handhaben Zeilenendezeichen unterschiedlich. Unter Unix wird das Zeichen \n (Zeilenvorschub, ASCII 10) verwendet und unter Macintosh das Zeichen \r (Wagenrücklauf, ASCII 13). MacPerl ist nur zum Lesen von Macintosh-Dateien ausgelegt, so dass es ein Wagenrücklaufzeichen anstelle eines Zeilenvorschubs erwartet. (Falls es Sie interessiert, Windows/DOS verwendet beide).

Antwort:
Die andere Lösung besteht darin, ganz oben in Ihrem Skript das Eingabedatensatz-Trennzeichen von MacPerl in ein Zeilenvorschubzeichen zu ändern:

$/ = "\n";

Workshop

Der Workshop enthält Quizfragen, die Ihnen helfen sollen, Ihr Wissen zu festigen, und Übungen, die Sie anregen sollen, das eben Gelernte umzusetzen und eigene Erfahrungen zu sammeln. Versuchen Sie, das Quiz und die Übungen zu beantworten und zu verstehen, bevor Sie zur Lektion des nächsten Tages übergehen.

Quiz

  1. Was ist ein Datei-Handle? Wozu dienen die Datei-Handle STDIN, STDOUT und STDERR?
  2. Worin liegt der Unterschied zwischen dem Erzeugen eines Datei-Handles zum Lesen, Schreiben oder Anhängen?
  3. Wozu benötigt man die Funktion die? Warum sollten man sie zusammen mit open verwenden?
  4. Wie lesen Sie Eingaben von einem Datei-Handle? Beschreiben Sie, wie sich Eingabe in skalarem Kontext, im skalaren Kontext einer while-Schleife und in einem Listenkontext verhalten.
  5. Wie schicken Sie Ausgaben an ein Datei-Handle?
  6. Worauf prüfen die folgenden Datei-Tests:
  7. -e
  8. -x
  9. -f
  10. -M
  11. -Z
  12. Wozu wird @ARGV verwendet? Was enthält die Variable?
  13. Was ist der Unterschied zwischen getopt und getopts?
  14. Welche Schalter erlauben die folgenden Aufrufe von getopts:
  15. getopts('xyz:');
  16. getopts('x:y:z');
  17. getopts('xXy');
  18. getopts('xyz');
  19. getopt('xyz');

Übungen

  1. Schreiben Sie ein Skript, das zwei Dateien (angegeben in der Befehlszeile) zeilenweise miteinander vermischt (eine Zeile von Datei 1, eine Zeile von Datei 2, eine Zeile von Datei 1 und so weiter). Schreiben Sie das Ergebnis in eine Datei namens gemischt.
  2. Schreiben Sie ein Skript, das zwei nur dann Dateien mischt, wenn die Extensionen der Dateinamen identisch sind. Verwenden Sie die gleiche Extension auch für die Ergebnisdatei gemischt. Sind die Extensionen unterschiedlich, verlassen Sie das Skript mit einer Fehlermeldung. (HINWEIS: Verwenden Sie reguläre Ausdrükke, um die Extensionen der Dateinamen zu ermitteln).
  3. Schreiben Sie ein Skript, das wie in der zweiten Übung zwei Dateien mischt. Das Skript soll eine einzige Option akzeptieren: -o. Ist die Option gesetzt, sollen auch Dateien mit unterschiedlichen Extensionen vermischt werden (die Extension der Ergebnisdatei können Sie frei wählen), und es wird keine Fehlermeldung ausgegeben.
  4. Schreiben Sie ein Skript, das ein einziges String-Argument und eine beliebige Kombination der folgenden vier Schalter übernimmt: -u, -s, -r und -c. -u liefert den String in Großbuchstaben zurück, -s entfernt Interpunktionszeichen und Whitespace, -r dreht den String um, und -c zählt die Anzahl der Zeichen. Stellen Sie sicher, dass die Optionen in verschiedenen Kombinationen verwendet werden können, um unterschiedliche Effekte zu erzielen.
  5. FEHLERSUCHE: Ein Skript wird folgendermaßen aufgerufen:
    meinskript.pl -sz eingabedatei
  6. Was ist falsch an diesem Skript? (HINWEIS: Die Prüfung von $opt_z liefert nicht wahr zurück).
        use strict;
    use Getopt::Std;
    use vars qw($opt_s $opt_z);
    getopt('sz');

    if ($opt_z) {
    # ...
    }

Antworten

Hier die Antworten auf die Workshop-Fragen aus dem vorigen Abschnitt.

Antworten zum Quiz

  1. In Perl verwendet man Datei-Handles, um Daten aus einer Quelle auszulesen oder Daten in ein Ziel zu schreiben (dabei kann es sich um eine Datei, die Tastatur, den Bildschirm, eine Netzwerkverbindung oder ein anderes Skript handeln). Die Datei- Handles STDIN, SDTOUT und STDERR beziehen sich auf die Standardeingabe, die Standardausgabe und die Standardfehlerausgabe. Datei-Handles zu allen anderen Dateien erzeugen Sie mit der Funktion open.
  2. Durch spezielle Zeichen vor dem Namen der zu öffnenden Datei zeigt man an, ob die Datei zum Lesen, Schreiben oder Anhängen geöffnet werden soll. Standardmäßig werden Dateien zum Lesen geöffnet.
  3. Die die-Funktion bricht das Skript behutsam ab und gibt dabei eine (hoffentlich) hilfreiche Fehlernachricht aus. Meistens wird die Funktion zusammen mit open verwendet, da man in dem Fall, dass eine Datei aus irgendwelchen Gründen nicht geöffnet werden kann, das Skript üblicherweise nicht weiter ausführen möchte. Es gehört daher zum guten Programmierstil, stets die Rückgabewerte von open zu überprüfen und die aufzurufen, falls beim Aufruf von open etwas schieflief.
  4. Verwenden Sie zum Lesen der Eingabe aus einem Datei-Handle den Eingabeoperator <> und den Namen des Datei-Handles. In einem skalaren Kontext liest der Eingabeoperator immer nur eine Zeile. Innerhalb einer while- Schleife weist er die einzelnen Zeilen nacheinander der Variablen $_ zu. In einem Listenkontext wird die gesamte Eingabe bis zum Ende der Datei eingelesen.
  5. Um eine Ausgabe an einen Datei-Handle zu schicken, braucht man die print- Funktion und den Namen des Datei-Handles. Beachten Sie, dass kein Komma zwischen dem Datei-Handle und dem, was ausgegeben werden soll, steht.
  6. -e testet, ob die Datei existiert.
  7. -x testet, ob es sich bei der Datei um eine ausführbare Datei handelt (normalerweise nur für Unix relevant).
  8. -f testet, ob es sich bei der Datei um eine einfache Datei (und nicht ein Verzeichnis, einen Link oder etwas anderes) handelt.
  9. -M prüft das Bearbeitungsdatum der Datei.
  10. -Z testet, ob die Datei existiert und leer ist.
  11. Die Array-Variable @ARGV speichert alle Argumente und Schalter, mit denen das Skript aufgerufen wurde.
  12. Die Funktion getopt definiert Schalter mit Werten, akzeptiert aber jede beliebige Option. Die Funktion getopts deklariert die möglichen Optionen für das Skript und legt fest, ob diese Werte haben oder nicht. Ein weiterer Unterschied besteht darin, dass getopts den Wert falsch zurückliefert, wenn Fehler bei der Bearbeitung der Befehlszeilenschalter aufgetreten sind. getopt liefert keinen praktischen Wert zurück.
  13. getopts('xyz:') definiert die Schalter -x, -y und -z mit einem Wert
  14. getopts('x:y:z') definiert -x und -y mit Wert und -z ohne Wert
  15. getopts('xXy') definiert -x und -X (beides sind separate Schalter) sowie -y. Keiner der Schalter hat einen Wert.
  16. getopts('xyz') definiert -x, -y und -z alle ohne Werte.
  17. getopt('xyz') definiert -x, -y und -z mit Werten sowie beliebige weitere einbuchstabige Schalter.

Antworten zu den Übungen

  1. Hier ist eine Antwort:
        #!/usr/bin/perl -w
    use strict;

    my ($file1, $file2) = @ARGV;

    open(FILE1, $file1) or
    die "Datei $file1 konnte nicht geoeffnet werden: $!\n";
    open(FILE2, $file2) or
    die "Datei $file2 konnte nicht geoeffnet werden: $!\n";
    open(MERGE, ">gemischt") or
    die "Die gemischte Datei konnte nicht geoeffnet werden: $!\n";

    my $line1 = <FILE1>;
    my $line2 = <FILE2>;
    while (defined($line1) || defined($line2)) {
    if (defined($line1)) {
    print MERGE $line1;
    $line1 = <FILE1>;
    }
    if (defined($line2)) {
    print MERGE $line2;
    $line2 = <FILE2>;
    }
    }
  2. Hier ist eine Antwort:
        #!/usr/bin/perl -w 
    use strict;

    my ($file1, $file2) = @ARGV;
    my $ext;

    if ($file1 =~ /\.(\w+)$/) {
    $ext = $1;
    if ($file2 !~ /\.$ext$/) {
      die "Extensionen sind nicht identisch.\n";
    }
    }

    open(FILE1, $file1) or
    die "Datei $file1 konnte nicht geoeffnet werden: $!\n";
    open(FILE2, $file2) or
    die "Datei $file2 konnte nicht geoeffnet werden: $!\n";
    open(MERGE, ">gemischt") or
    die "Die gemischte Datei konnte nicht geoeffnet werden: $!\n";

    my $line1 = <FILE1>;
    my $line2 = <FILE2>;
    while (defined($line1) || defined($line2)) {
    if (defined($line1)) {
      print MERGE $line1;
      $line1 = <FILE1>;
    }
    if (defined($line2)) {
      print MERGE $line2;
      $line2 = <FILE2>;
    }
    }
  3. Hier ist eine Antwort:
        #!/usr/bin/perl -w 
    use strict;
    use Getopt::Std;
    use vars qw($opt_o);
    getopts('o');

    my ($file1, $file2) = @ARGV;
    my $ext;

    if ($file1 =~ /\.(\w+)$/) {
    $ext = $1;
    if ($file2 !~ /\.$ext$/) {
      if (!$opt_o) {
      die "Extensionen sind nicht identisch.\n";
      }
    }
    }

    open(FILE1, $file1) or
    die "Datei $file1 konnte nicht geoeffnet werden: $!\n";
    open(FILE2, $file2) or
    die "Datei $file2 konnte nicht geoeffnet werden: $!\n";
    open(MERGE, ">gemischt") or
    die "Die gemischte Datei konnte nicht geoeffnet werden: $!\n";

    my $line1 = <FILE1>;
    my $line2 = <FILE2>;
    while (defined($line1) || defined($line2)) {
    if (defined($line1)) {
      print MERGE $line1;
      $line1 = <FILE1>;
    }
    if (defined($line2)) {
      print MERGE $line2;
      $line2 = <FILE2>;
    }
    }
  4. Hier eine mögliche Lösung:
        #!/usr/bin/perl -w
    use strict;
    use Getopt::Std;
    use vars qw($opt_s $opt_r $opt_u $opt_c);
    getopts('sruc');

    my $str = $ARGV[0];

    if ($opt_s) {
    $str =~ s/[\s.,;:!?'"]//g;
    }

    if ($opt_r) {
    $str = reverse $str;
    }

    if ($opt_u) {
    $str = uc $str;
    }

    if ($opt_c) {
    $str = length $str;
    }

    print "$str\n";
  5. Das Skriptfragment verwendet die Funktion getopt. Im Gegensatz zu getopts geht diese Funktion davon aus, dass alle Schalter mit Werten versehen sind. Wenn also das Skript mit dem Schalter -sz aufgerufen wird, geht getopt davon aus, dass Sie den Schalter -s verwenden, der den Wert z hat. Der Schalter -z wird von getopt nie registriert. Verwenden Sie die Funktion getopts, um sicherzustellen, dass sowohl $opt_s und $opt_z gesetzt werden.


vorheriges KapitelInhaltsverzeichnisStichwortverzeichnisFeedbacknächstes Kapitel


1

© Markt&Technik Verlag, ein Imprint der Pearson Education Deutschland GmbH